home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / xulrunner / python / templateoptimize.py < prev    next >
Encoding:
Python Source  |  2007-11-12  |  7.4 KB  |  208 lines

  1. # Miro - an RSS based video player application
  2. # Copyright (C) 2005-2007 Participatory Culture Foundation
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  17.  
  18. import re
  19. import logging
  20.  
  21. from xml.sax.expatreader import ExpatParser
  22. from xml.sax.handler import ContentHandler
  23.  
  24. hotspotMarkerPattern = re.compile("<!-- HOT SPOT ([^ ]*) -->")
  25.  
  26. class ChangeItemHint:
  27.     """Contains hints the frontend can use to optimize
  28.     HTMLDisplay.changeItem().  Member attributes:
  29.  
  30.  
  31.     changedInnerHTML -- The inner HTML of the element being changed.  If
  32.         nothing changed inside the element this will be None.
  33.     changedAttributes -- A dict containing the changes to the DOM element
  34.         attributes.  Each attribute that changed will have an entry.  The key
  35.         is the attribute name.  If the attribute was removed, the value will
  36.         be None.
  37.     """
  38.  
  39.     def __init__(self, changedAttributes, changedInnerHTML):
  40.         self.changedAttributes = changedAttributes
  41.         self.changedInnerHTML = changedInnerHTML
  42.  
  43. class SingleElementHandler(ContentHandler):
  44.     """XML Content handler that just reads in the first elements tagname and
  45.     attributes.
  46.     """
  47.     def __init__(self):
  48.         self.started = False
  49.         ContentHandler.__init__(self)
  50.     def startElement(self, name, attrs):
  51.         self.name = name
  52.         self.attrs = attrs
  53.         self.started = True
  54.     def reset(self):
  55.         self.started = False
  56.  
  57. class BrokenUpElement:
  58.     """Breaks up an HTML element into it's outermost tag and inner html.
  59.     
  60.     Attributes:
  61.       html -- original HTML
  62.       name -- outermost tag name
  63.       attrs -- outermost tag attributes
  64.       innerHTML -- data inside the outermost tag.
  65.  
  66.     """
  67.  
  68.     parser = ExpatParser()
  69.     handler = SingleElementHandler()
  70.     parser.setContentHandler(handler)
  71.  
  72.     def __init__(self, id, html):
  73.         self.id = id
  74.         self.html = html
  75.         innerHTMLStart = self.feedInFirstElement(html)
  76.         innerHTMLEnd = html.rfind("</")
  77.         self.innerHTML = html[innerHTMLStart:innerHTMLEnd]
  78.         self.name = self.handler.name
  79.         self.attrs = self.handler.attrs
  80.         self.handler.reset()
  81.         self.parser.reset()
  82.  
  83.     def feedInFirstElement(self, html):
  84.         pos = 0
  85.         while not self.handler.started:
  86.             end_candidate = html.find(">", pos)
  87.             if end_candidate < 0:
  88.                 raise ValueError("Can't find start tag in %s" % html)
  89.             self.parser.feed(html[pos:end_candidate+1])
  90.             pos = end_candidate+1
  91.         return pos
  92.  
  93.     def calcChanges(self, older):
  94.         """Calculate the changes between self and an older version of this
  95.         element.  Return a list of changes to pass to HTMLArea.changeItems.
  96.         """
  97.  
  98.         attrDiff = self.calcAttributeChanges(older.attrs, self.attrs)
  99.         newInnerHTML = self.calcNewInnerHTML(older.innerHTML, self.innerHTML)
  100.         if attrDiff or newInnerHTML:
  101.             hint = ChangeItemHint(attrDiff, newInnerHTML)
  102.             return [(self.id, self.html, hint)]
  103.         else:
  104.             return []
  105.  
  106.     def calcNewInnerHTML(self, oldInnerHTML, newInnerHTML):
  107.         """Calculate the newInnerHTML argument to pass to HTMLArea.changeItem.
  108.         """
  109.         if oldInnerHTML != newInnerHTML:
  110.             return newInnerHTML
  111.         else:
  112.             return None
  113.  
  114.     def calcAttributeChanges(self, oldAttrs, newAttrs):
  115.         """Calculate the difference between two attribute dicts.  Returns a dict
  116.         with entries for each key that has changed.  Keys that have been removed
  117.         will have None values.
  118.         """
  119.  
  120.         changes = dict(newAttrs)
  121.         for key, value in oldAttrs.items():
  122.             try:
  123.                 if changes[key] == value:
  124.                     del changes[key] # attribute stayed the same
  125.             except KeyError:
  126.                 changes[key] = None # attribute was removed
  127.         return changes
  128.  
  129. class OptimizedElement:
  130.     """Used by HTMLChangeOptimizer to calculate how an html element changed."""
  131.  
  132.     def __init__(self, id, html):
  133.         self.brokenUp = BrokenUpElement(id, html)
  134.         self.calcHotSpots()
  135.  
  136.     def calcHotSpots(self):
  137.         self.hotspots = {}
  138.         split = hotspotMarkerPattern.split(self.brokenUp.html)
  139.         self.outerParts = [split[0]]
  140.         i = 1
  141.         while i < len(split):
  142.             hotspotID, hotspotHTML, end, outerPart = split[i:i+4]
  143.             self.hotspots[hotspotID] = BrokenUpElement(hotspotID, hotspotHTML)
  144.             self.outerParts.append(hotspotID)
  145.             self.outerParts.append(outerPart)
  146.             i += 4
  147.  
  148.     def calcChanges(self, older):
  149.         if len(self.outerParts) == 1 or self.outerParts != older.outerParts:
  150.             return self.brokenUp.calcChanges(older.brokenUp)
  151.         else:
  152.             changes = []
  153.             for id in self.hotspots:
  154.                 new = self.hotspots[id].calcChanges(older.hotspots[id])
  155.                 changes.extend(new)
  156.             return changes
  157.  
  158. class HTMLChangeOptimizer:
  159.     """Class that handles changing xml in an efficient way.  It currently
  160.     optimizes a few cases:
  161.  
  162.       * If the html stays the same, we don't send anything to the frontend
  163.         code.
  164.       * We calculate a ChangeItemHint for each element that changes.  Smart
  165.         frontends can use this to optimize the changeItem() call, especially
  166.         when only the change is repeated element's attributes.
  167.       * Child elements can be marked "hotspots" meaning they are more likely 
  168.         change than other parts.  We only update the hotspot elements when we
  169.         detect that other html hasn't changed.
  170.  
  171.     We mark a region as a hotspot by adding specially formatted comments like
  172.     so:
  173.  
  174.     <!-- HOT SPOT hotspot-dom-id --><div id="hotspot-dom-id">
  175.        blah blah blah
  176.     </div><!-- HOT SPOT END -->
  177.     """
  178.  
  179.     def __init__(self):
  180.         self.elements = {}
  181.  
  182.     def setInitialHTML(self, id, html):
  183.         """Set the initial HTML for a dom element.  This must be called before
  184.         calcChanges() is called with id
  185.         """
  186.         self.elements[id] = OptimizedElement(id, html)
  187.  
  188.     def calcChanges(self, id, html):
  189.         """Calculate a list of arguments to pass to HTMLArea.changeItems()."""
  190.         try:
  191.             old = self.elements[id]
  192.         except KeyError:
  193.             # this case shouldn't happen in the wild, but it was reported in
  194.             # #8689.  Don't try to optimize anything.
  195.             logging.warn("KeyError for element %s in "
  196.                     "HTMLChangeOptimizer.calcChanges()", id)
  197.             return [ (id, html, None) ]
  198.         new = OptimizedElement(id, html)
  199.         self.elements[id] = new
  200.         return new.calcChanges(old)
  201.  
  202.     def removeElements(self, ids):
  203.         for id in ids:
  204.             if id in self.elements:
  205.                 del self.elements[id]
  206.             else:
  207.                 logging.warn("Trying to remove an unknown element.")
  208.